﻿using System;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace Kaesoft.Hospedaje.Proxy
{
    public static class WcfProxyInvoker
    {
        #region Variables
        private static string _usuario;

        public static ProxyAdministracion AdministrationProxy;
        public static ProxyParametrizacion ParametrizacionProxy;
        public static ProxyCliente ClienteProxy;
        public static ProxyDatosReglas DatosReglasProxy;
        //public static ProxyReclamoBonificacion ReclamoBonificacionProxy;
        #endregion

        #region Propiedades
        /// <summary>
        /// Usuario autenticado?
        /// </summary>
        public static bool UsuarioAutenticado { private get; set; }

        /// <summary>
        /// Nombre de usuario para la DMZ
        /// </summary>
        public static string Usuario
        {
            get
            {
                return _usuario;
            }
            set
            {
                _usuario = value;
                if (UsuarioLogged != null)
                    UsuarioLogged(null, new EventArgs());
            }
        }

        /// <summary>
        /// Clave para la DMZ
        /// </summary>
        public static string Clave { get; set; }

        #endregion

        #region Eventos
        public static event EventHandler<ErrorConexionEventArgs> ErrorConexion;
        public static event EventHandler ErrorNegocio;
        public static event EventHandler UsuarioLogged;
        public static event EventHandler ErrorAutenticacion;
        public static event EventHandler SesionExpirada;
        #endregion

        #region Metodos Privados
        /// <summary>
        /// Envia excepcion tipo timeout al cliente
        /// </summary>
        /// <param name="value">Estado de la conexion</param>
        /// <param name="ex">Excepcion</param>
        private static void ThrowTimeoutException(EstadoConexion value, Exception ex = null)
        {
            if (ErrorConexion != null)
            {
                ErrorConexion(null, new ErrorConexionEventArgs { States = value, InnerException = ex });
            }
        }

        /// <summary>
        /// Envia excepcion tipo autenticación al cliente
        /// </summary>
        /// <param name="value">Estado de la conexion</param>
        /// <param name="ex">Excepcion</param>
        private static void ThrowAuthenticationException(Exception ex = null)
        {
            if (ErrorAutenticacion != null)
            {
                ErrorAutenticacion(ex, null);
            }
        }

        /// <summary>
        /// Envía erorr de negocio
        /// </summary>
        /// <param name="exception">Excepcion</param>
        private static void ThrowBusinessException(Exception exception)
        {
            if (ErrorNegocio != null)
                ErrorNegocio(exception, new EventArgs());
        }

        /// <summary>
        /// Envía error de timeout
        /// </summary>
        /// <param name="ex">Excepcion</param>
        private static void ThrowServerNoDisponibleException(Exception ex)
        {
            if (UsuarioAutenticado)
            {
                ThrowTimeoutException(EstadoConexion.ConnectionCrashed, ex);
                return;
            }
            ThrowTimeoutException(EstadoConexion.ConnectionContinueDown);
        }

        private static void ThrowSessionExpiredException(Exception exception)
        {
            if (SesionExpirada != null)
                SesionExpirada(exception, new EventArgs());
        }

        #endregion

        #region Metodos Publicos
        /// <summary>
        /// Graba las credenciales del usuario DMZ
        /// </summary>
        /// <param name="credenciales"></param>
        public static void GrabarCredenciales(ClientCredentials credenciales)
        {
            if (credenciales != null && string.IsNullOrEmpty(credenciales.UserName.UserName) && !string.IsNullOrEmpty(Usuario))
            {
                credenciales.UserName.UserName = Usuario;
                credenciales.UserName.Password = Clave;
            }
        }

        /// <summary>
        /// Invoca método async
        /// </summary>
        public static object InvocarMetodo(object servicioCliente, string metodo, params object[] parametros)
        {
            return InvocarMetodoParametros(servicioCliente, metodo, null, parametros);
        }

        /// <summary>
        /// Obtener mensaje de error de la excepcion
        /// </summary>
        /// <param name="err">Excepcion</param>
        /// <returns>Mensaje</returns>
        public static string ObtenerTipoExcepcion(Exception err)
        {
            if (err.InnerException != null)
            {
                return err.InnerException.GetType().ToString();
            }
            else
            {
                if (err.GetType().ToString().Equals("System.ServiceModel.FaultException`1[System.ServiceModel.ExceptionDetail]"))
                {
                    var ex = (System.ServiceModel.FaultException<System.ServiceModel.ExceptionDetail>)(err);
                    if (ex.Detail != null)
                        return ex.Detail.Type;
                }
                else
                    return err.GetType().ToString();
            }
            return err.GetType().ToString();
        }

        /// <summary>
        /// Obtener mensaje de error de la excepcion
        /// </summary>
        /// <param name="err">Excepcion</param>
        /// <returns>Mensaje</returns>
        public static string ObtenerMensajeError(Exception err)
        {
            if (err.InnerException != null)
            {
                return err.InnerException.Message;
            }
            else
            {
                if (err.GetType().ToString().Equals("System.ServiceModel.FaultException`1[System.ServiceModel.ExceptionDetail]"))
                {
                    var ex = (System.ServiceModel.FaultException<System.ServiceModel.ExceptionDetail>)(err);
                    if (ex.Detail != null)
                    {
                        if (ex.Detail.InnerException != null)
                        {
                            return ex.Detail.InnerException.Message;
                        }
                    }
                }
                else
                {
                    return err.Message;
                }
            }
            return err.Message;
        }

        /// <summary>
        /// Determina si existieron errores al ejecutar un metodo async
        /// </summary>
        /// <param name="exception">Excepcion</param>
        /// <returns>Existe o no error</returns>
        public static bool ExistenErroresAsync(Exception exception)
        {
            if (exception != null)
            {
                var tipo = ObtenerTipoExcepcion(exception);

                if (tipo.Contains("CommunicationException"))
                    ThrowServerNoDisponibleException(exception);
                else if (exception is TimeoutException || tipo.Contains("TimeoutException"))
                    ThrowTimeoutException(EstadoConexion.ConnectionContinueDown, exception);
                else if (exception is FaultException && exception.Message.Contains("Session expired"))
                    ThrowSessionExpiredException(exception);
                else if (exception is AuthenticationException)
                    ThrowAuthenticationException(exception);
                else
                    ThrowBusinessException(exception);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Invoca un metodo async
        /// </summary>
        public static object InvocarMetodoParametros(object servicioCliente, string metodo, ParameterModifier[] modificador, params object[] parametros)
        {
            object resultado;
            try
            {
                Type cliente = servicioCliente.GetType();

                resultado = cliente.InvokeMember(metodo, BindingFlags.InvokeMethod, null, servicioCliente, parametros,
                                                 modificador, null, null);
            }
            catch (TargetInvocationException invocationException)
            {
                Exception exception = invocationException;
                throw exception;
            }

            return resultado;
        }

        #endregion
    }

    #region Enums & Args
    /// <summary>
    /// Estado de la conexion
    /// </summary>
    public enum EstadoConexion
    {
        ConnectionCrashed,
        ConnectionContinueDown
    }

    /// <summary>
    /// Argumentos para envio de datos por perdida de conexion
    /// </summary>
    public class ErrorConexionEventArgs : EventArgs
    {
        public EstadoConexion States { get; set; }
        public Exception InnerException { get; set; }
    }
    #endregion
}